package io.kiki.task.client.common.rpc.supports.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ServiceLoaderUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.kiki.task.client.common.HandlerInterceptor;
import io.kiki.task.client.common.cache.EndPointInfoCache;
import io.kiki.task.client.common.config.Properties;
import io.kiki.task.client.common.exception.ClientException;
import io.kiki.task.client.common.rpc.supports.http.Request;
import io.kiki.task.client.common.rpc.supports.http.Response;
import io.kiki.task.client.common.rpc.supports.scan.EndPointInfo;
import io.kiki.task.common.core.constant.SystemConstants;
import io.kiki.task.common.core.enums.StatusEnum;
import io.kiki.task.common.core.model.HttpResponse;
import io.kiki.task.common.core.model.JobRequest;
import io.kiki.task.common.core.model.Result;
import io.kiki.task.common.core.util.JsonUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class DispatcherRequestHandler {
    private final Properties properties;

    private static List<HandlerInterceptor> handlerInterceptors() {
        List<HandlerInterceptor> handlerInterceptors = ServiceLoaderUtil.loadList(HandlerInterceptor.class);
        if (CollUtil.isEmpty(handlerInterceptors)) {
            return Collections.emptyList();
        }

        return handlerInterceptors.stream().sorted(Comparator.comparingInt(HandlerInterceptor::order)).collect(
                Collectors.toList());
    }

    public HttpResponse dispatch(HttpRequest httpRequest) {
        Request request = httpRequest.getRequest();
        Response response = httpRequest.getResponse();


        List<HandlerInterceptor> handlerInterceptors = handlerInterceptors();
        JobRequest retryRequest = JsonUtil.parseObject(httpRequest.getContent(), JobRequest.class);

        EndPointInfo endPointInfo = null;
        Result resultObj = null;
        Exception e = null;
        HttpResponse httpResponse = new HttpResponse();
        try {
            String snailJobAuth = httpRequest.getHttpHeaders().getAsString(SystemConstants.SNAIL_JOB_AUTH_TOKEN);
            String configToken = Optional.ofNullable(properties.getToken()).orElse(SystemConstants.DEFAULT_TOKEN);
            if (!configToken.equals(snailJobAuth)) {
                throw new ClientException("认证失败.【请检查配置的Token是否正确】");
            }

            UrlBuilder urlBuilder = UrlBuilder.ofHttp(httpRequest.getUri());
            RequestMethod requestMethod = RequestMethod.valueOf(httpRequest.getHttpMethod().name());

            endPointInfo = EndPointInfoCache.get(urlBuilder.getPathStr(), requestMethod);
            if (Objects.isNull(endPointInfo)) {
                throw new ClientException("无法找到对应的处理请检查对应的包是否正确引入. " +
                        "path:[{}] requestMethod:[{}]", urlBuilder.getPathStr(), requestMethod);
            }

            Class<?>[] paramTypes = endPointInfo.getMethod().getParameterTypes();
            Object[] args = retryRequest.getArgs();

            Object[] deSerialize = (Object[]) deSerialize(JsonUtil.toJsonString(args), endPointInfo.getMethod(), request, response);

            for (final HandlerInterceptor handlerInterceptor : handlerInterceptors) {
                if (!handlerInterceptor.preHandle(request, response, endPointInfo)) {
                    return httpResponse;
                }
            }

            if (paramTypes.length > 0) {
                resultObj = (Result) ReflectionUtils.invokeMethod(endPointInfo.getMethod(),
                        endPointInfo.getExecutor(), deSerialize);
            } else {
                resultObj = (Result) ReflectionUtils.invokeMethod(endPointInfo.getMethod(),
                        endPointInfo.getExecutor());
            }

            for (final HandlerInterceptor handlerInterceptor : handlerInterceptors) {
                handlerInterceptor.postHandle(request, response, endPointInfo);
            }
        } catch (Exception ex) {
            log.error("http request error. [{}]", httpRequest.getContent(), ex);
            httpResponse.setMessage(ex.getMessage()).setStatus(StatusEnum.NO.getStatus());
            e = ex;
        } finally {
            httpResponse.setRequestId(retryRequest.getReqId());
            if (Objects.nonNull(resultObj)) {
                httpResponse.setData(resultObj.getData())
                        .setMessage(resultObj.getMessage())
                        .setStatus(resultObj.getStatus());
            }

            for (final HandlerInterceptor handlerInterceptor : handlerInterceptors) {
                handlerInterceptor.afterCompletion(request, response, endPointInfo, e);
            }
        }

        return httpResponse;
    }

    public Object deSerialize(String infoStr, Method method,
                              Request request, Response response) throws JsonProcessingException {

        Type[] paramTypes = method.getGenericParameterTypes();
        Parameter[] parameters = method.getParameters();
        Object[] params = new Object[paramTypes.length];

        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonNode = JsonUtil.toJson(infoStr);
        if (Objects.isNull(jsonNode)) {
            log.warn("jsonNode is null. infoStr:[{}]", infoStr);
            return params;
        }

        for (int i = 0; i < paramTypes.length; i++) {
            JsonNode node = jsonNode.get(i);
            if (Objects.nonNull(node)) {
                params[i] = mapper.readValue(node.toString(), mapper.constructType(paramTypes[i]));
                continue;
            }

            Parameter parameter = parameters[i];
            if (parameter.getType().isAssignableFrom(Request.class)) {
                params[i] = request;
                continue;
            }

            if (parameter.getType().isAssignableFrom(Response.class)) {
                params[i] = response;
            }
        }

        return params;
    }

}
